﻿/* 
Copyright (c) 2009, Aaron Moline <Aaron.Moline@MolineSoftware.com>

Permission is hereby granted, free of charge, to any person 
obtaining a copy of this software and associated documentation 
files (the "Software"), to deal in the Software without restriction, 
including without limitation the rights to use, copy, modify, merge, 
publish, distribute, sublicense, and/or sell copies of the Software, 
and to permit persons to whom the Software is furnished to do so, 
subject to the following conditions:

The above copyright notice and this permission notice shall be 
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
DEALINGS IN THE SOFTWARE.
*/

using System;
using System.Text;
using System.ComponentModel;
using System.Collections.Generic;
using System.Net;

using CookComputing.XmlRpc;
using System.Security.Cryptography;


using MolineSoftware.DrupalXmlRpc.Classes;

using MolineSoftware.DrupalXmlRpc.Interfaces;


namespace MolineSoftware.DrupalXmlRpc
{
    /// <summary>
    /// Class: Base Class for Drupal Service.
    /// </summary>
    /// <typeparam name="T">Of Type <see cref="IXmlRpcProxy"/></typeparam>
    public class DrupalXmlRpcService<T>
        where T : IXmlRpcProxy, IDrupalXmlRpc
    {
        #region | Private Properties |

        #endregion

        #region | Constructor |
        /// <summary>
        /// Intialize a new intanace of DrupalXmlRpcService
        /// </summary>
        public DrupalXmlRpcService()
        {

        }
        #endregion   

        #region | Public Propertes |

        /// <summary>
        /// The <see cref="IXmlRpcProxy"/> service member: <see cref="T"/>
        /// </summary>
        public T Service;

        /// <summary>
        /// Gets or Sets the Connection Information
        /// </summary>
        public Drupal ConnInfo { get; set; }

        /// <summary>
        /// Gets or Sets the user login information.
        /// </summary>
        public Drupal LoginInfo { get; set; }

        /// <summary>
        /// Gets or Sets the current open <see cref="DrupalNode"/>
        /// </summary>
        public DrupalNode CurrentNode { get; set; }
        
        private string _serviceKey;
        /// <summary>
        /// Gets or Sets the Sevice Key Used for the Application.
        /// </summary>
        public string ServiceKey// { get; set; }
        {
            get
            {
                if (string.IsNullOrEmpty(_serviceKey))
                    return DrupalServiceAttribute.Get(this).ServiceKey;
                return _serviceKey;

            }
            set { _serviceKey = value; }

        }

        private string _serviceDomain;
        /// <summary>
        /// Gets or Sets the Service Domain.
        /// </summary>
        public string ServiceDomain// { get; set; }
        {
            set
            {
                _serviceDomain = value;
            }
            get
            {
                if(_serviceDomain == null)
                    return DrupalServiceAttribute.Get(this).ServiceDomain;

                return _serviceDomain;
            }
        }

        /// <summary>
        /// Gets or Sets the Nonce.
        /// </summary>
        /// <seealso cref="IntializeHash"/>
        public string Nonce { get; set; }

        /// <summary>
        /// Gets or Sets the Unix Time Stamp
        /// </summary>
        public string TimeStamp { get; set; }

        /// <summary>
        /// Gets or Sets the API Hash.
        /// </summary>
        public string APIHash { get; set; }

        /// <summary>
        /// Gets or Sets the <see cref="WebClient"/>
        /// </summary>
        public WebClient drupalWebClient { get; set; }

        /// <summary>
        /// Gets or Sets the search results.
        /// </summary>
        public XmlRpcStruct[]  SearchResults { get; set; }

        public string ServiceURL { get; set; }
        #endregion

        #region | Internal Methods |

        /// <summary>
        /// Gets the current Unix time stamp.
        /// </summary>
        /// <returns></returns>
        public string GetUnixTimestamp()
        {
            TimeSpan ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
            return Convert.ToString(Convert.ToUInt64(ts.TotalSeconds));
        }

        /// <summary>
        /// Similar to the 'user_password' function Drupal uses.
        /// </summary>
        /// <param name="length">The length of the  Onnce</param>
        /// <returns><see cref="string"/></returns>
        internal string GetNonce(int length)
        {
            string allowedCharacters = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
            StringBuilder password = new StringBuilder();
            Random rand = new Random();

            for (int i = 0; i < length; i++)
            {
                password.Append(allowedCharacters[rand.Next(0, (allowedCharacters.Length - 1))]);
            }

            return password.ToString();
        }

        /// <summary>
        /// Computes the Hash string for connection to the Drupal XmlRpc service.
        /// </summary>
        /// <param name="message">The hashstring.<see cref="GetHashString"/></param>
        /// <returns><see cref="string"/></returns>
        internal string GetHMAC(string message)
        {
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
            byte[] keyByte = encoding.GetBytes(this.ServiceKey);
            byte[] messageByte = encoding.GetBytes(message);

            HMACSHA256 hmac = new HMACSHA256(keyByte);
            byte[] hashMessageByte = hmac.ComputeHash(messageByte);

            string sbinary = String.Empty;
            for (int i = 0; i < hashMessageByte.Length; i++)
            {
                // Converting to hex, but using lowercase 'x' to get lowercase characters
                sbinary += hashMessageByte[i].ToString("x2");
            }

            return sbinary;
        }

        /// <summary>
        /// Creates has string for intializing a connection to a Drupal XmlRpc Service.
        /// </summary>
        /// <param name="command">The <see cref="DrupalRpcCommand"/></param>
        /// <returns><see cref="string"/></returns>
        internal string GetHashString(DrupalRpcCommand command)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(this.TimeStamp);
            sb.Append(";");
            sb.Append(this.ServiceDomain);
            sb.Append(";");
            sb.Append(this.Nonce);
            sb.Append(";");
            sb.Append(CustomDescription.GetDescription(command));
            return sb.ToString();
        }

        /// <summary>
        /// Creates has string for intializing a connection to a Drupal XmlRpc Service.
        /// </summary>
        /// <param name="command">The <see cref="DrupalRpcCommand"/></param>
        /// <returns><see cref="string"/></returns>
        internal string GetHashString(string command)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(this.TimeStamp);
            sb.Append(";");
            sb.Append(this.ServiceDomain);
            sb.Append(";");
            sb.Append(this.Nonce);
            sb.Append(";");
            sb.Append(command);
            return sb.ToString();
        }
        #endregion

        #region | Public Methods |

        /// <summary>
        /// Intializes the Connection.
        /// </summary>
        public void Intilialize()
        {
            if (string.IsNullOrEmpty(this.ServiceKey))
                throw new ArgumentNullException("ServiceKey");

            if (string.IsNullOrEmpty(this.ServiceDomain))
                throw new ArgumentNullException("ServiceDomain");

            Service = XmlRpcProxyGen.Create<T>();

            if (!string.IsNullOrEmpty(ServiceURL))
                Service.Url = ServiceURL;
        }

        /// <summary>
        /// Intialize the APIHash for Authentication.
        /// </summary>
        /// <param name="command">The <see cref="DrupalRpcCommand"/> command</param>
        public void IntializeHash(DrupalRpcCommand command)
        {            
            this.Nonce = GetNonce(10);
            this.TimeStamp = GetUnixTimestamp();
            string hashstring = GetHashString(command);
            this.APIHash = GetHMAC(hashstring);
        }

        /// <summary>
        /// Intialize the APIHash for Authentication.
        /// </summary>
        /// <param name="command">The <see cref="DrupalRpcCommand"/> command</param>
        public void IntializeHash(string command)
        {
            this.Nonce = GetNonce(10);
            this.TimeStamp = GetUnixTimestamp();
            string hashstring = GetHashString(command);
            this.APIHash = GetHMAC(hashstring);
        }

        /// <summary>
        /// Connect to Drupla XmlRpc Service.
        /// </summary>
        /// <param name="async">True to connect Asynchronously</param>
        public void Connect(bool async)
        {
           
            Intilialize();

            if (async)
            {
                AsyncCallback acb = new AsyncCallback(ConnectionCallBack);
                IAsyncResult asr = Service.BeginConnect(acb);

                if (asr.CompletedSynchronously)
                {
                    ConnInfo = Service.EndConnect(asr);
                }
            }
            else
            {
                ConnInfo = Service.Connect();
            }           

        }

        /// <summary>
        /// Log into Drupal Asynchronously
        /// </summary>
        /// <param name="username">The username</param>
        /// <param name="password">The password</param>
        /// <param name="async">True to login Asynchronously </param>
        public void Login(string username, string password, bool async)
        {

            try
            {
                IntializeHash(DrupalRpcCommand.Login);

                if (async)
                {
                    AsyncCallback acb = new AsyncCallback(LoginCallBack);
                    IAsyncResult asr = Service.BeginLogin(
                        this.APIHash,
                         this.ServiceDomain,
                        this.TimeStamp,
                        this.Nonce,
                        ConnInfo.SessionID,
                        username,
                        password,
                        acb);
                }
                else
                {
                    LoginInfo = Service.Login(APIHash,
                                                ServiceDomain,
                                                TimeStamp,
                                                Nonce,
                                                ConnInfo.SessionID,
                                                username,
                                                password);

                }


            }
            catch (Exception)
            {

                throw;
            }

        }

        /// <summary>
        /// Logouts out user.
        /// </summary>
        /// <param name="async"></param>
        /// <returns></returns>
        public bool Logout(bool async)
        {
            try
            {
                if (string.IsNullOrEmpty(LoginInfo.SessionID))
                    return false;

                IntializeHash(DrupalRpcCommand.Logout);

                if (async)
                {

                    AsyncCallback acb = new AsyncCallback(LogoutCallBack);
                    IAsyncResult results = Service.BeginLogout(
                        this.APIHash,
                        this.ServiceDomain,
                        this.TimeStamp,
                        this.Nonce,
                        LoginInfo.SessionID,
                        acb);

                    return false;
                }
                else
                {
                    return Service.Logout(
                        this.APIHash,
                        this.ServiceDomain,
                        this.TimeStamp,
                        this.Nonce,
                        LoginInfo.SessionID);
                }
            }
            catch (Exception ex)
            {                
                throw ex;
            }
        }

        /// <summary>
        /// SendMail using the Drupal XmlRpc Service.
        /// </summary>
        /// <param name="receipent">The receipent of the email.</param>
        /// <param name="subject">The subject.</param>
        /// <param name="body">The body.</param>
        /// <param name="senderAddress">Who the email will be sent from.</param>
        /// <param name="headers">The header information of the email.</param>
        /// <param name="async">True to connect asynchronously.</param>
        public DrupalMail SendMail(string receipent, string subject, string body, string senderAddress,
            DrupalMailHeader headers, bool async)
        {
            try
            {
                IntializeHash(DrupalRpcCommand.SendMail);

                string mailKey = Guid.NewGuid().ToString().Replace("-"," ");

                if (async)
                {
                    AsyncCallback acb = new AsyncCallback(SendMailCallBack);
                    IAsyncResult asr = Service.BeginMailSend(
                        this.APIHash,
                        this.ServiceDomain,
                        this.TimeStamp,
                        this.Nonce,
                        LoginInfo.SessionID,
                        mailKey,
                        receipent,
                        subject,
                        body,
                        senderAddress,
                        headers,
                        acb);
                }
                else
                {
                    return Service.MailSend(
                        this.APIHash,
                        this.ServiceDomain,
                        this.TimeStamp,
                        this.Nonce,
                        LoginInfo.SessionID,
                        mailKey,
                        receipent,
                        subject,
                        body,
                        senderAddress,
                        headers);
                }
                return new DrupalMail();

            }
            catch (Exception)
            {                
                throw;
            }
        }
                
        /// <summary>
        /// Gets Node using the Drupal XmlRpc service
        /// </summary>
        /// <typeparam name="T">The <see cref="DrupalBase"/></typeparam>
        /// <param name="nodeid">The NodeID to get.</param>
        /// <param name="fields">What fields to return data for.</param>
        /// <param name="async">True to connect asynchronously.</param>
        /// <returns><see cref="DrupalNode"/></returns>
        public T GetNode<T>(int nodeid, string[] fields, bool async) 
            where T: DrupalBase
        {
            try
            {
                IntializeHash(DrupalRpcCommand.NodeGet);
                if (async)
                {
                     AsyncCallback acb = new AsyncCallback(GetNodeCallBack);
                     Service.BeginNodeGet(
                         this.APIHash,
                         this.ServiceDomain,
                         this.TimeStamp,
                         this.Nonce,                         
                         LoginInfo.SessionID,                         
                         nodeid,
                         fields,
                         acb);
                }
                else
                {
                    return Service.NodeGet(
                        this.APIHash,
                            this.ServiceDomain,
                            this.TimeStamp,
                            this.Nonce,
                            LoginInfo.SessionID,
                            nodeid,
                            fields).Convert<T>();

                }
                T node = Activator.CreateInstance<T>();
                //node.Body = "Error";
                return node;
            }
            catch (Exception)
            {
                
                throw;
            }
        }

        /// <summary>
        /// Gets users.
        /// </summary>
        /// <typeparam name="T">The <see cref="T"/> to return object as</typeparam>
        /// <param name="userID">The uid of the Drupal user to get.</param>
        /// <param name="async">True to get asynchronously</param>
        /// <returns><see cref="T"/></returns>
        public T GetUser<T>(int userID, bool async) where T: DrupalUser
        {
            IntializeHash(DrupalRpcCommand.UserGet);

            if (async)
            {
                AsyncCallback acb = new AsyncCallback(UserGetCallBack);
                Service.BeginUserGet(this.APIHash,
                            this.ServiceDomain,
                            this.TimeStamp,
                            this.Nonce,
                            this.LoginInfo.SessionID,
                            userID, acb);

              
            }
            else
            {
                return Service.UserGet(this.APIHash,
                            this.ServiceDomain,
                            this.TimeStamp,
                            this.Nonce,
                            this.LoginInfo.SessionID,
                            userID).Convert<T>();
            }
            return Activator.CreateInstance<T>();
        }

        ///// <summary>
        ///// Saves Node using the Drupal XmlRpc service
        ///// </summary>
        ///// <param name="node">The new <see cref="DrupalNode"/> to save</param>
        ///// <param name="async">True to connect asynchronously.</param>
        ///// <returns><see cref="string"/></returns>
        //public string SaveNode(XmlRpcStruct node, bool async)
        //{
        //    try
        //    {
        //        //ValidateNodeSave(node);

        //        IntializeHash(DrupalRpcCommand.NodeSave);

        //        if (async)
        //        {
        //            AsyncCallback acb = new AsyncCallback(SaveNodeCallBack);
        //            IAsyncResult asr = Service.BeginNodeSave(this.APIHash,
        //                    this.ServiceDomain,
        //                    this.TimeStamp,
        //                    this.Nonce,
        //                    LoginInfo.SessionID,
        //                    node,
        //                    acb);                  
        //        }
        //        else
        //        {
        //            return Service.NodeSave(this.APIHash,
        //                this.ServiceDomain,
        //                this.TimeStamp,
        //                this.Nonce,
        //                LoginInfo.SessionID,
        //                node);
        //        }

        //        return string.Empty;
        //    }
        //    catch (Exception)
        //    {
                
        //        throw;
        //    }

        //}

        /// <summary>
        /// Saves Node using the Drupal XmlRpc service
        /// </summary>
        /// <typeparam name="T">The <see cref="T"/></typeparam>
        /// <param name="dnode">The node to save.</param>
        /// <param name="async">True to save asynchronously</param>
        /// <returns>NodeID</returns>
        public string SaveNode<T>(T dnode, bool async) 
            where T : DrupalNode
        {
            try
            {
                //ValidateNodeSave(node);
                XmlRpcStruct node = dnode.Convert<T>();

                IntializeHash(DrupalRpcCommand.NodeSave);

                if (async)
                {
                    AsyncCallback acb = new AsyncCallback(SaveNodeCallBack);
                    IAsyncResult asr = Service.BeginNodeSave(this.APIHash,
                            this.ServiceDomain,
                            this.TimeStamp,
                            this.Nonce,
                            LoginInfo.SessionID,
                            node,
                            acb);
                }
                else
                {            
                    return Service.NodeSave(this.APIHash,
                         this.ServiceDomain,
                         this.TimeStamp,
                         this.Nonce,
                         LoginInfo.SessionID,
                         node); 
                }

                return string.Empty;
            }
            catch (Exception)
            {
                throw;
            }

        }
        /// <summary>
        /// Gets the specified <see cref="DrupalFileNode"/>
        /// </summary>
        /// <param name="fileID">The FileID</param>
        /// <param name="async">True to retrive asynchronously</param>
        /// <returns><see cref="DrupalFileNode"/></returns>
        public DrupalFileNode GetFile(int fileID, bool async)
        {
            try
            {
                IntializeHash(DrupalRpcCommand.FileGet);
                if (async)
                {
                    AsyncCallback acb = new AsyncCallback(GetFileCallBack);
                    IAsyncResult result = Service.BeginFileGet(
                        this.APIHash,
                        this.ServiceDomain,
                        this.TimeStamp,
                        this.Nonce,
                        LoginInfo.SessionID,
                        fileID,
                        acb);

                }
                else
                {
                    return Service.FileGet(
                        this.APIHash,
                        this.ServiceDomain,
                        this.TimeStamp,
                        this.Nonce,
                        LoginInfo.SessionID,
                        fileID);

                }


                return new DrupalFileNode();
            }
            catch (Exception)
            {
                
                throw;
            }
        }

        /// <summary>
        /// Download File from Drupal Website. <see cref="GetFile"/>
        /// </summary>
        /// <param name="url">The full URL path to the file.</param>
        /// <param name="fullfilename">The path and file name to where the file should be downloaded to</param>
        /// <param name="async">True to download asynchronously.</param> 
        public void DownloadFile(string url, string fullfilename, bool async)
        {
            try
            {               
                if (async)
                {
                    IntializeWebClient();

                    drupalWebClient.DownloadFileAsync(new Uri(url), fullfilename);
                }
                else
                {
                    using (drupalWebClient = new WebClient())
                    {
                        drupalWebClient.DownloadFile(url, fullfilename);
                    }
                }
            }
            catch (Exception)
            {

                throw;
            }
        }

        /// <summary>
        /// Search Nodes
        /// </summary>
        /// <param name="search_keys">The text to serach for</param>
        /// <param name="simpleText">The "simple" text to search for.</param>
        /// <param name="async">True to search asynchronously.</param>
        /// <returns>Array of <see cref="DrupalNodeSearchResult"/></returns>
        public XmlRpcStruct[] SearchNodes(string search_keys, string simpleText, string[] fields, bool async)
        {
            IntializeHash(DrupalRpcCommand.SearchNodes);

            if (async)
            {
                AsyncCallback acb = new AsyncCallback(SearchNodesCallBack);
                IAsyncResult result = Service.BeginSearchNodes(this.APIHash,
                       this.ServiceDomain,
                       this.TimeStamp,
                       this.Nonce,
                       this.LoginInfo.SessionID,
                       search_keys, simpleText, fields, acb);

                return new XmlRpcStruct[] { };
            }
            else
            {
                try
                {
                    return Service.SearchNodes(this.APIHash,
                           this.ServiceDomain,
                           this.TimeStamp,
                           this.Nonce,
                           this.LoginInfo.SessionID,
                           search_keys, simpleText, fields);
                }
                catch //(Exception ex)
                {
                    //If not results are found, the service throws an error.
                    return new XmlRpcStruct[] { };
                }
            }

        }

        /// <summary>
        /// Returns a <see cref="TView"/>
        /// </summary>
        /// <typeparam name="TView"></typeparam>
        /// <param name="viewname">Then anem of the view to get</param>
        /// <param name="args"></param>
        /// <param name="async">True to search asynchronously</param>
        /// <returns><see cref="TView"/></returns>
        public List<TView> GetView<TView>(string viewname, string[] args, bool async) where TView : DrupalBase
        {
            List<TView> lView = null;

            IntializeHash(DrupalRpcCommand.ViewsGet);
            if (async)
            {
                lView = Activator.CreateInstance<List<TView>>();
                AsyncCallback asb = new AsyncCallback(GetViewCallBack);
                Service.BeginViewsGet(this.APIHash,
                       this.ServiceDomain,
                       this.TimeStamp,
                       this.Nonce,
                       this.LoginInfo.User.SessionID,
                       viewname,
                       new string[] { },
                       args,
                       0,
                       0,
                       asb);


            }
            else
            {
                return Service.ViewsGet(this.APIHash,
                       this.ServiceDomain,
                       this.TimeStamp,
                       this.Nonce,
                       this.LoginInfo.SessionID,
                       viewname,
                       new string[] { },
                       args,
                       0,
                       0).Convert<TView>();
            }


            return lView;
        }

        /// <summary>
        /// Delete's a specified node.
        /// </summary>
        /// <param name="nid">The nid of the Node to delete</param>
        /// <param name="async">True to search asynchronously</param>
        /// <returns></returns>
        public string NodeDelete(int nid, bool async)
        {
            try
            {
                IntializeHash(DrupalRpcCommand.NodeDelete);

                if (async)
                {
                    AsyncCallback acb = new AsyncCallback(NodeDeleteCallBack);
                    IAsyncResult asr = Service.BeginNodeDelete(this.APIHash,
                            this.ServiceDomain,
                            this.TimeStamp,
                            this.Nonce,
                            LoginInfo.SessionID,
                            nid, acb);
                }
                else
                {
                    return Service.NodeDelete(this.APIHash,
                            this.ServiceDomain,
                            this.TimeStamp,
                            this.Nonce,
                            LoginInfo.SessionID,
                            nid);
                }
                return string.Empty;
            }
            catch (Exception)
            {
                
                throw;
            }
 
        }
        #endregion

        #region | Web Events |
        void drupalWebClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            RaiseOnAsycFinish(sender, new DrupalXmlRpcServiceEvent(DrupalRpcCommand.DownloadFile));
        }

        #endregion

        #region | Async Methods |
        /// <summary>
        /// Call Back for Asynchronous connections. 
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void ConnectionCallBack(IAsyncResult result)
        {
            try
            {
                //XmlRpcAsyncResult clientResult = (XmlRpcAsyncResult)result;
                //XmlRpcClientProtocol drupal = (XmlRpcClientProtocol)clientResult.ClientProtocol;
                ConnInfo = Service.EndConnect(result);
                RaiseOnAsycFinish(result, new DrupalXmlRpcServiceEvent(ConnInfo, DrupalRpcCommand.Connect));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }
    
        /// <summary>
        /// Call Back for Asynchronous connections. 
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void LoginCallBack(IAsyncResult result)
        {
            try
            {
                LoginInfo = Service.EndLogin(result);
                RaiseOnAsycFinish(result, new DrupalXmlRpcServiceEvent(LoginInfo, DrupalRpcCommand.Login));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        public XmlRpcStruct FoundDrupalUser { get; set; }

        public void UserGetCallBack(IAsyncResult result)
        {
            try
            {
                FoundDrupalUser = Service.EndUserGet(result);
                RaiseOnAsycFinish(result, new DrupalXmlRpcServiceEvent(LoginInfo, DrupalRpcCommand.UserGet));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        /// <summary>
        /// Call Back for Asynchronous mail sending
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void SendMailCallBack(IAsyncResult result)
        {
            try
            {
               DrupalMail dmail = Service.EndMailSend(result);
               RaiseOnAsycFinish(dmail, new DrupalXmlRpcServiceEvent(dmail, DrupalRpcCommand.SendMail));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        /// <summary>
        /// Call Back for Asynchronous calls for nodes
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void GetNodeCallBack(IAsyncResult result)
        {
            try
            {
                CurrentNode = Service.EndNodeGet(result).Convert<DrupalNode>();
                RaiseOnAsycFinish(CurrentNode, new DrupalXmlRpcServiceEvent(CurrentNode, DrupalRpcCommand.NodeGet));

            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        /// <summary>
        /// Call back for Asynchronous calls for saving nodes
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void SaveNodeCallBack(IAsyncResult result)
        {
            try
            {
                string nodeId = Service.EndNodeSave(result);

                if (string.IsNullOrEmpty(nodeId))
                    throw new Exception("Unable to Save Node");

                this.CurrentNode = new DrupalNode();
                CurrentNode.NodeID = nodeId;

                RaiseOnAsycFinish(CurrentNode, new DrupalXmlRpcServiceEvent(DrupalRpcCommand.NodeSave));

            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        /// <summary>
        /// Call back for Asynchronous calls for getting files
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void GetFileCallBack(IAsyncResult result)
        {
            try
            {
                DrupalFileNode file = Service.EndFileGet(result);
                RaiseOnAsycFinish(file, new DrupalXmlRpcServiceEvent(DrupalRpcCommand.FileGet));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }

        }

        /// <summary>
        /// Call back for Asynchronous calls for getting files
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void LogoutCallBack(IAsyncResult result)
        {
            try
            {
                bool status = Service.EndLogout(result);
                RaiseOnAsycFinish(status, new DrupalXmlRpcServiceEvent(DrupalRpcCommand.Logout));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        /// <summary>
        /// Call back for Asynchronous calls for searching nodes
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void SearchNodesCallBack(IAsyncResult result)
        {
            try
            {
                SearchResults = Service.EndSearchNodes(result);
                RaiseOnAsycFinish(SearchResults, new DrupalXmlRpcServiceEvent(DrupalRpcCommand.SearchNodes));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        /// <summary>
        /// Call back for Asynchronous calls for getting view
        /// </summary>
        /// <param name="result"><see cref="IAsyncResult"/></param>
        public void GetViewCallBack(IAsyncResult result)
        {
            try
            {
                Service.EndViewsGet(result);
                RaiseOnAsycFinish(SearchResults, new DrupalXmlRpcServiceEvent(DrupalRpcCommand.ViewsGet));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        public void NodeDeleteCallBack(IAsyncResult result)
        {
            try
            {
                Service.EndNodeDelete(result);
                RaiseOnAsycFinish(SearchResults, new DrupalXmlRpcServiceEvent(DrupalRpcCommand.NodeDelete));
            }
            catch (Exception ex)
            {
                RaiseOnAsycFinish(GetExceptionMessage(ex),
                    new DrupalXmlRpcServiceEvent(DrupalRpcCommand.AsyncError));
            }
        }

        #endregion

        #region | Event Handlers |
        /// <summary>
        /// Occures when the asynchronous call finishes.
        /// </summary>
        public event EventHandler<DrupalXmlRpcServiceEvent> OnAsyncFinish;

        /// <summary>
        /// Occures when the webclient is Intilialized.
        /// </summary>
        public event EventHandler OnWebClientIntialize;

        /// <summary>
        /// Raises the <see cref="OnWebClientIntialize"/> event.
        /// </summary>
        /// <param name="sender">The <see cref="object"/></param>
        /// <param name="e">The <see cref="EventArgs"/></param>
        public void RaiseOnWebClientIntialize(object sender, EventArgs e)
        {
            if (OnWebClientIntialize != null)
                OnWebClientIntialize(sender, e);
        }

        /// <summary>
        /// Raises the <see cref="DrupalXmlRpcServiceEvent"/> event.
        /// </summary>
        /// <param name="sender">The <see cref="object"/></param>
        /// <param name="e">The <see cref="DrupalXmlRpcServiceEvent"/></param>
        public void RaiseOnAsycFinish(object sender, DrupalXmlRpcServiceEvent e)
        {
            if (OnAsyncFinish != null)
                OnAsyncFinish(sender, e);
        }

        #endregion

        #region | Private Methods |
        /// <summary>
        /// Validate that new nodes contain the required information.
        /// </summary>
        /// <param name="node">The <see cref="DrupalNode"/> to validate</param>
        private void ValidateNodeSave(DrupalNode node)
        { 
            string errorMessage = string.Empty;
            //A node object. Upon creation, node object must include "type". Upon update, node object must include "nid" and "changed".
            if (string.IsNullOrEmpty(node.NodeID))
            { 
                if (string.IsNullOrEmpty(node.Type))
                    errorMessage = "You must set the Node Type";
            }
             //if(string.IsNullOrEmpty(node.NodeID))
             //    errorMessage = errorMessage += "Updated node must contain a nodeid";

            if (string.IsNullOrEmpty(node.CreatenDate))
                node.CreatenDate = this.TimeStamp;

             if (!string.IsNullOrEmpty(errorMessage))
                 throw new Exception(errorMessage);
        }

        /// <summary>
        /// Intilize Web Client for Download.
        /// </summary>
        private void IntializeWebClient()
        {
            if (drupalWebClient != null)
            {
                drupalWebClient.Dispose();

            }

            drupalWebClient = new WebClient();
            RaiseOnWebClientIntialize(null, EventArgs.Empty);

            drupalWebClient.DownloadFileCompleted +=
                new AsyncCompletedEventHandler(drupalWebClient_DownloadFileCompleted);
        }

        /// <summary>
        /// Returns Error Message from Exception
        /// </summary>
        /// <param name="ex">The <see cref="Exception"/></param>
        /// <returns><see cref="string"/></returns>
        public string GetExceptionMessage(Exception ex)
        {
            string errorMessage;
            try
            {
                throw ex;
            }
            catch (XmlRpcFaultException fex)
            {
                errorMessage = String.Format("Fault Response: {0} {1}",
                                  fex.FaultCode, fex.FaultString);
            }
            catch (WebException webEx)
            {
                errorMessage = String.Format("WebException: {0}", webEx.Message);
                if (webEx.Response != null)
                    webEx.Response.Close();
            }
            catch (Exception excep)
            {
                errorMessage = String.Format("Exception: {0}", excep.Message);
            }

            return errorMessage;
        }
        #endregion
     
    }
}
